﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SimplexEngine
{
    public class SceneNode : Node
    {
        protected internal SceneNode(SceneManager scene_manager) : base()
        {
            creator = scene_manager;
        }

        protected internal SceneNode(SceneManager scene_manager, String node_name) : base(node_name)
        {
            creator = scene_manager;
        }

        public SceneNode CreateChildNode()
        {
            SceneNode child_node = creator.InternalCreateSceneNode();
            AddChild(child_node);
            return child_node;
        }

        public SceneNode CreateChildNode(String node_name)
        {
            SceneNode child_node = creator.InternalCreateSceneNode(node_name);
            AddChild(child_node);
            return child_node;        
        }

        public SceneManager Creator { get { return creator; } }
        protected SceneManager creator = null;

        protected BoundingBox local_aabb;
        protected BoundingBox world_aabb;

        protected Boolean cached_world_aabb_out_of_date = true;
        protected Boolean cached_local_aabb_out_of_date = true;

        protected virtual void UpdateLocalAABB()
        {
            if (cached_local_aabb_out_of_date)
            {
                // calculate local aabb
                local_aabb = new BoundingBox();

                foreach (SceneObject scene_object in scene_objects.Values)
                    local_aabb = BoundingBox.CreateMerged(local_aabb, scene_object.AABB);

                cached_local_aabb_out_of_date = false;
                cached_world_aabb_out_of_date = true;
            }
        }

        protected virtual void UpdateWorldAABB()
        {
            if (cached_world_aabb_out_of_date)
            {
                Vector3[] local_vectors = local_aabb.GetCorners();
                Vector3[] world_vectors = new Vector3[local_vectors.Count()];

                for (int i = 0; i < local_vectors.Count(); ++i)
                    world_vectors[i] = Vector3.Transform(local_vectors[i], Transform);
                
                world_aabb = BoundingBox.CreateFromPoints(world_vectors);

                cached_world_aabb_out_of_date = false;
            }
        }

        protected virtual void UpdateAABB()
        {
            UpdateLocalAABB();
            UpdateWorldAABB();
        }

        public BoundingBox AABB
        {
            get 
            {
                UpdateAABB();
                return world_aabb;
            }
        }

        protected override void NeedUpdate()
        {
            base.NeedUpdate();
            cached_world_aabb_out_of_date = true;
        }

        protected Dictionary<String, SceneObject> scene_objects = new Dictionary<String, SceneObject>();
        public void AddSceneObject(SceneObject scene_object)
        {
            scene_objects.Add(scene_object.Name, scene_object);
            scene_object.Parent = this;
        }

        public void RemoveSceneObject(SceneObject scene_object)
        {
            scene_objects.Remove(scene_object.Name);
            scene_object.Parent = null;
        }

        public void RemoveSceneObject(String scene_object_name)
        {
            RemoveSceneObject(scene_objects[scene_object_name]);
        }

        public virtual Boolean Visible { get { return visible; } set { visible = value; } }
        protected Boolean visible = true;

        public virtual void Draw(RenderSystem render_system, int view_filter, GameTime game_time, Int64 frame_number)
        {
            if (!Visible)
                return;

            OnDraw(render_system, view_filter, game_time, frame_number);

            foreach (SceneNode node in children.Values)
                node.Draw(render_system, view_filter, game_time, frame_number);
        }

        protected virtual void OnDraw(RenderSystem render_system, int view_filter, GameTime game_time, Int64 frame_number) 
        {
            foreach (SceneObject scene_object in scene_objects.Values)
                if ((scene_object.ObjectFilter & view_filter) != 0)
                    scene_object.Draw(game_time, frame_number);
        }

        protected override void OnUpdate(GameTime game_time, Int64 frame_number)
        {
            foreach (SceneObject scene_object in scene_objects.Values)
                scene_object.Update(game_time, frame_number);
        }
    }
}
